package com.yfqy.admin.security.resourceapi.internal;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.fasterxml.jackson.core.type.TypeReference;
import com.yfqy.admin.constant.MyConstants;
import com.yfqy.admin.domain.dto.auth.LoginUserInfoDTO;
import com.yfqy.admin.enums.LoginTypeEnum;
import com.yfqy.admin.exception.BaseException;
import com.yfqy.admin.security.login.LoginUserInfo;
import com.yfqy.admin.service.JwtService;
import com.yfqy.admin.util.ExceptionTool;
import com.yfqy.admin.util.JSON;
import com.yfqy.admin.util.LoginUserInfoTool;
import io.jsonwebtoken.ExpiredJwtException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.NonNull;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.List;
import java.util.Map;

@Component
public class MyJwtAuthenticationFilter extends OncePerRequestFilter {

    private static final Logger logger = LoggerFactory.getLogger(MyJwtAuthenticationFilter.class);

    private final JwtService jwtService;
    private final RedissonClient redissonClient;

    public MyJwtAuthenticationFilter(JwtService jwtService, @Qualifier("singleRedisClient") RedissonClient redissonClient) {
        this.jwtService = jwtService;
        this.redissonClient = redissonClient;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, @NonNull HttpServletResponse response,
                                    @NonNull FilterChain filterChain) throws ServletException, IOException {
        logger.debug("Use MyJwtAuthenticationFilter");
        String jwtToken = request.getHeader("Authorization");
        if (StringUtils.isEmpty(jwtToken)) {
            throw new IllegalArgumentException("JWT token is missing!");
        }
        if (jwtToken.startsWith("Bearer ")) {
            jwtToken = jwtToken.substring(7);
        }
        try {
            LoginUserInfoDTO dto = jwtService.verifyJwt(jwtToken, LoginUserInfoDTO.class);
            if (!checkLoginType(request.getRequestURI(), dto.getLoginType())) {
                throw new IllegalArgumentException("不合法的token");
            }
            // 后台管理系统
            if (LoginTypeEnum.USERNAME == dto.getLoginType()) {
                Object tokenData = redissonClient.getBucket(String.format(MyConstants.SYS_USER_TOKEN, dto.getId())).get();
                if (tokenData != null) {
                    List<Map<String, String>> authorities = JSON.parse(
                            tokenData.toString(),
                            new TypeReference<List<Map<String, String>>>() {
                            }
                    );
                    dto.setAuthorities(authorities);
                }
            }
            LoginUserInfo loginUserInfo = LoginUserInfoTool.convert(dto);
            MyJwtAuthentication authentication = new MyJwtAuthentication(loginUserInfo);
            authentication.setJwtToken(jwtToken);
            // 设置true，认证通过。
            authentication.setAuthenticated(true);
            // 认证通过后，一定要设置到SecurityContextHolder里面去。
            SecurityContextHolder.getContext().setAuthentication(authentication);
        } catch (ExpiredJwtException e) {
            // 转换异常，指定code，让前端知道时token过期，去调刷新token接口
            ExceptionTool.throwException(HttpStatus.UNAUTHORIZED.value(), "token过期");
        } catch (Exception e) {
            if (e instanceof BaseException e1) {
                ExceptionTool.throwException(e1.getCode(), e1.getMessage());
            }
            ExceptionTool.throwException(HttpStatus.UNAUTHORIZED.value(), "token解析失败");
        }
        // 放行
        filterChain.doFilter(request, response);
    }


    private boolean checkLoginType(String uri, LoginTypeEnum loginType) {
        if (uri.matches(MyConstants.INTERNAL_URL_PATTEN) && loginType == LoginTypeEnum.USERNAME) {
            return true;
        }
        return uri.matches(MyConstants.CUSTOMER_URL_PATTEN)
                && (loginType == LoginTypeEnum.SMS || loginType == LoginTypeEnum.OPENID);
    }
}
